diff options
Diffstat (limited to 'app/[lng]')
| -rw-r--r-- | app/[lng]/evcp/(evcp)/esg-check-list/page.tsx | 74 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/evaluation-target-list/page.tsx | 172 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/incoterms/page.tsx | 53 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/payment-conditions/page.tsx | 53 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/vendor-check-list/page.tsx | 74 | ||||
| -rw-r--r-- | app/[lng]/partners/(partners)/evaluation/page.tsx | 135 | ||||
| -rw-r--r-- | app/[lng]/partners/(partners)/rfq-ship/[id]/page.tsx | 2 |
7 files changed, 562 insertions, 1 deletions
diff --git a/app/[lng]/evcp/(evcp)/esg-check-list/page.tsx b/app/[lng]/evcp/(evcp)/esg-check-list/page.tsx new file mode 100644 index 00000000..515751d5 --- /dev/null +++ b/app/[lng]/evcp/(evcp)/esg-check-list/page.tsx @@ -0,0 +1,74 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { getEsgEvaluations } from "@/lib/esg-check-list/service" +import { getEsgEvaluationsSchema } from "@/lib/esg-check-list/validation" +import { EsgEvaluationsTable } from "@/lib/esg-check-list/table/esg-table" + + +interface IndexPageProps { + searchParams: Promise<SearchParams> +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = getEsgEvaluationsSchema.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getEsgEvaluations({ + ...search, + filters: validFilters, + }), + + ]) + + return ( + <Shell className="gap-2"> + <div className="flex items-center justify-between space-y-2"> + <div className="flex items-center justify-between space-y-2"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + ESG 자가진단표 + </h2> + <p className="text-muted-foreground"> + 협력업체 평가에 사용되는 ESG 자가진단표를 관리{" "} + {/* <span className="inline-flex items-center whitespace-nowrap"> + <Ellipsis className="size-3" /> + <span className="ml-1">버튼</span> + </span> + 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */} + </p> + </div> + </div> + </div> + + <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> + {/* <DateRangePicker + triggerSize="sm" + triggerClassName="ml-auto w-56 sm:w-60" + align="end" + shallow={false} + /> */} + </React.Suspense> + <React.Suspense + fallback={ + <DataTableSkeleton + columnCount={6} + searchableColumnCount={1} + filterableColumnCount={2} + cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} + shrinkZero + /> + } + > + <EsgEvaluationsTable promises={promises} /> + </React.Suspense> + </Shell> + ) +} diff --git a/app/[lng]/evcp/(evcp)/evaluation-target-list/page.tsx b/app/[lng]/evcp/(evcp)/evaluation-target-list/page.tsx new file mode 100644 index 00000000..d60f695a --- /dev/null +++ b/app/[lng]/evcp/(evcp)/evaluation-target-list/page.tsx @@ -0,0 +1,172 @@ +import * as React from "react" +import { Metadata } from "next" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { Shell } from "@/components/shell" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { HelpCircle } from "lucide-react" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" + +import { getDefaultEvaluationYear, searchParamsEvaluationTargetsCache } from "@/lib/evaluation-target-list/validation" +import { getEvaluationTargets } from "@/lib/evaluation-target-list/service" +import { EvaluationTargetsTable } from "@/lib/evaluation-target-list/table/evaluation-target-table" + +export const metadata: Metadata = { + title: "협력업체 평가 대상 확정", + description: "협력업체 평가 대상을 확정하고 담당자를 지정합니다.", +} + +interface EvaluationTargetsPageProps { + searchParams: Promise<SearchParams> +} + +// 프로세스 안내 팝오버 컴포넌트 +function ProcessGuidePopover() { + return ( + <Popover> + <PopoverTrigger asChild> + <Button variant="ghost" size="icon" className="h-6 w-6"> + <HelpCircle className="h-4 w-4 text-muted-foreground" /> + </Button> + </PopoverTrigger> + <PopoverContent className="w-96" align="start"> + <div className="space-y-3"> + <div className="space-y-1"> + <h4 className="font-medium">평가 대상 확정 프로세스</h4> + <p className="text-sm text-muted-foreground"> + 발주실적을 기반으로 평가 대상을 확정하는 절차입니다. + </p> + </div> + <div className="space-y-3 text-sm"> + <div className="flex gap-3"> + <div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600"> + 1 + </div> + <div> + <p className="font-medium">발주실적 기반 자동 추출</p> + <p className="text-muted-foreground">전년도 10월 ~ 해당년도 9월 발주실적에서 업체 목록을 자동으로 생성합니다.</p> + </div> + </div> + <div className="flex gap-3"> + <div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600"> + 2 + </div> + <div> + <p className="font-medium">담당자 지정</p> + <p className="text-muted-foreground">각 평가 대상별로 5개 부서(발주/조달/품질/설계/CS)의 담당자를 지정합니다.</p> + </div> + </div> + <div className="flex gap-3"> + <div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600"> + 3 + </div> + <div> + <p className="font-medium">검토 및 의견 수렴</p> + <p className="text-muted-foreground">모든 담당자가 평가 대상 적합성을 검토하고 의견을 제출합니다.</p> + </div> + </div> + <div className="flex gap-3"> + <div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600"> + 4 + </div> + <div> + <p className="font-medium">최종 확정</p> + <p className="text-muted-foreground">모든 담당자 의견이 일치하면 평가 대상으로 최종 확정됩니다.</p> + </div> + </div> + </div> + </div> + </PopoverContent> + </Popover> + ) +} + +export default async function EvaluationTargetsPage(props: EvaluationTargetsPageProps) { + const searchParams = await props.searchParams + const search = searchParamsEvaluationTargetsCache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + // 기본 필터 처리 (통일된 이름 사용) + let basicFilters = [] + if (search.basicFilters && search.basicFilters.length > 0) { + basicFilters = search.basicFilters + console.log("Using search.basicFilters:", basicFilters); + } else { + console.log("No basic filters found"); + } + + // 모든 필터를 합쳐서 처리 + const allFilters = [...validFilters, ...basicFilters] + + // 조인 연산자도 통일된 이름 사용 + const joinOperator = search.basicJoinOperator || search.joinOperator || 'and'; + + // 현재 평가년도 (필터에서 가져오거나 기본값 사용) + const currentEvaluationYear = search.evaluationYear || getDefaultEvaluationYear() + + // Promise.all로 감싸서 전달 + const promises = Promise.all([ + getEvaluationTargets({ + ...search, + filters: allFilters, + joinOperator, + }) + ]) + + return ( + <Shell className="gap-4"> + {/* 간소화된 헤더 */} + <div className="flex items-center justify-between space-y-2"> + <div className="flex items-center justify-between space-y-2"> + <div className="flex items-center gap-2"> + <h2 className="text-2xl font-bold tracking-tight"> + 협력업체 평가 대상 확정 + </h2> + <Badge variant="outline" className="text-sm"> + {currentEvaluationYear}년도 + </Badge> + <ProcessGuidePopover /> + </div> + </div> + </div> + + {/* 메인 테이블 (통계는 테이블 내부로 이동) */} + <React.Suspense + key={JSON.stringify(searchParams)} // URL 파라미터가 변경될 때마다 강제 리렌더링 + fallback={ + <DataTableSkeleton + columnCount={12} + searchableColumnCount={2} + filterableColumnCount={6} + cellWidths={[ + "3rem", // checkbox + "5rem", // 평가년도 + "4rem", // 구분 + "8rem", // 벤더코드 + "12rem", // 벤더명 + "4rem", // 내외자 + "6rem", // 자재구분 + "5rem", // 상태 + "5rem", // 의견일치 + "8rem", // 담당자현황 + "10rem", // 관리자의견 + "8rem" // actions + ]} + shrinkZero + /> + } + > + <EvaluationTargetsTable + promises={promises} + evaluationYear={currentEvaluationYear} + /> + </React.Suspense> + </Shell> + ) +}
\ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/incoterms/page.tsx b/app/[lng]/evcp/(evcp)/incoterms/page.tsx new file mode 100644 index 00000000..57a19009 --- /dev/null +++ b/app/[lng]/evcp/(evcp)/incoterms/page.tsx @@ -0,0 +1,53 @@ +import * as React from "react"; +import { type SearchParams } from "@/types/table"; +import { getValidFilters } from "@/lib/data-table"; +import { Shell } from "@/components/shell"; +import { Skeleton } from "@/components/ui/skeleton"; +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"; +import { SearchParamsCache } from "@/lib/incoterms/validations"; +import { getIncoterms } from "@/lib/incoterms/service"; +import { IncotermsTable } from "@/lib/incoterms/table/incoterms-table"; + +interface IndexPageProps { + searchParams: Promise<SearchParams>; +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams; + const search = SearchParamsCache.parse(searchParams); + const validFilters = getValidFilters(search.filters); + + const promises = Promise.all([ + getIncoterms({ + ...search, + filters: validFilters, + }), + ]); + + return ( + <Shell className="gap-2"> + <div className="flex items-center justify-between space-y-2"> + <div> + <h2 className="text-2xl font-bold tracking-tight">인코텀즈 관리</h2> + <p className="text-muted-foreground"> + 인코텀즈(Incoterms)를 등록, 수정, 삭제할 수 있습니다. + </p> + </div> + </div> + <React.Suspense fallback={<Skeleton className="h-7 w-52" />}></React.Suspense> + <React.Suspense + fallback={ + <DataTableSkeleton + columnCount={4} + searchableColumnCount={1} + filterableColumnCount={2} + cellWidths={["10rem", "40rem", "12rem", "8rem"]} + shrinkZero + /> + } + > + <IncotermsTable promises={promises} /> + </React.Suspense> + </Shell> + ); +}
\ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/payment-conditions/page.tsx b/app/[lng]/evcp/(evcp)/payment-conditions/page.tsx new file mode 100644 index 00000000..b9aedfbb --- /dev/null +++ b/app/[lng]/evcp/(evcp)/payment-conditions/page.tsx @@ -0,0 +1,53 @@ +import * as React from "react"; +import { type SearchParams } from "@/types/table"; +import { getValidFilters } from "@/lib/data-table"; +import { Shell } from "@/components/shell"; +import { Skeleton } from "@/components/ui/skeleton"; +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"; +import { SearchParamsCache } from "@/lib/payment-terms/validations"; +import { getPaymentTerms } from "@/lib/payment-terms/service"; +import { PaymentTermsTable } from "@/lib/payment-terms/table/payment-terms-table"; + +interface IndexPageProps { + searchParams: Promise<SearchParams>; +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams; + const search = SearchParamsCache.parse(searchParams); + const validFilters = getValidFilters(search.filters); + + const promises = Promise.all([ + getPaymentTerms({ + ...search, + filters: validFilters, + }), + ]); + + return ( + <Shell className="gap-2"> + <div className="flex items-center justify-between space-y-2"> + <div> + <h2 className="text-2xl font-bold tracking-tight">결제 조건 관리</h2> + <p className="text-muted-foreground"> + 결제 조건(Payment Terms)을 등록, 수정, 삭제할 수 있습니다. + </p> + </div> + </div> + <React.Suspense fallback={<Skeleton className="h-7 w-52" />}></React.Suspense> + <React.Suspense + fallback={ + <DataTableSkeleton + columnCount={4} + searchableColumnCount={1} + filterableColumnCount={2} + cellWidths={["10rem", "40rem", "12rem", "8rem"]} + shrinkZero + /> + } + > + <PaymentTermsTable promises={promises} /> + </React.Suspense> + </Shell> + ); +}
\ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/vendor-check-list/page.tsx b/app/[lng]/evcp/(evcp)/vendor-check-list/page.tsx new file mode 100644 index 00000000..3fd7e425 --- /dev/null +++ b/app/[lng]/evcp/(evcp)/vendor-check-list/page.tsx @@ -0,0 +1,74 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { getGenralEvaluationsSchema } from "@/lib/general-check-list/validation" +import { GeneralEvaluationsTable } from "@/lib/general-check-list/table/general-check-list-table" +import { getGeneralEvaluations } from "@/lib/general-check-list/service" + + +interface IndexPageProps { + searchParams: Promise<SearchParams> +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = getGenralEvaluationsSchema.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getGeneralEvaluations({ + ...search, + filters: validFilters, + }), + + ]) + + return ( + <Shell className="gap-2"> + <div className="flex items-center justify-between space-y-2"> + <div className="flex items-center justify-between space-y-2"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + 협력업체 정기평가 체크리스트 + </h2> + <p className="text-muted-foreground"> + 협력업체 평가에 사용되는 정기평가 체크리스트를 관리{" "} + {/* <span className="inline-flex items-center whitespace-nowrap"> + <Ellipsis className="size-3" /> + <span className="ml-1">버튼</span> + </span> + 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */} + </p> + </div> + </div> + </div> + + <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> + {/* <DateRangePicker + triggerSize="sm" + triggerClassName="ml-auto w-56 sm:w-60" + align="end" + shallow={false} + /> */} + </React.Suspense> + <React.Suspense + fallback={ + <DataTableSkeleton + columnCount={6} + searchableColumnCount={1} + filterableColumnCount={2} + cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} + shrinkZero + /> + } + > + <GeneralEvaluationsTable promises={promises} /> + </React.Suspense> + </Shell> + ) +} diff --git a/app/[lng]/partners/(partners)/evaluation/page.tsx b/app/[lng]/partners/(partners)/evaluation/page.tsx new file mode 100644 index 00000000..dc1d4137 --- /dev/null +++ b/app/[lng]/partners/(partners)/evaluation/page.tsx @@ -0,0 +1,135 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { getServerSession } from "next-auth" +import { authOptions } from "@/app/api/auth/[...nextauth]/route" +import Link from "next/link" +import { Button } from "@/components/ui/button" +import { LogIn } from "lucide-react" +import { getEvaluationSubmissions } from "@/lib/vendor-evaluation-submit/service" +import { getEvaluationsSubmitSchema } from "@/lib/vendor-evaluation-submit/validation" +import { EvaluationSubmissionsTable } from "@/lib/vendor-evaluation-submit/table/submit-table" + +interface IndexPageProps { + searchParams: Promise<SearchParams> +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = getEvaluationsSubmitSchema.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + // Get session + const session = await getServerSession(authOptions) + + // Check if user is logged in + if (!session || !session.user) { + // Return login required UI instead of redirecting + return ( + <Shell className="gap-6"> + <div className="flex items-center justify-between"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + 정기평가 + </h2> + <p className="text-muted-foreground"> + 요청된 정기평가를 입력하고 제출할 수 있습니다. + </p> + </div> + </div> + + <div className="flex flex-col items-center justify-center py-12 text-center"> + <div className="rounded-lg border border-dashed p-10 shadow-sm"> + <h3 className="mb-2 text-xl font-semibold">로그인이 필요합니다</h3> + <p className="mb-6 text-muted-foreground"> + 정기평가를 확인하려면 먼저 로그인하세요. + </p> + <Button size="lg" asChild> + <Link href="/partners"> + <LogIn className="mr-2 h-4 w-4" /> + 로그인하기 + </Link> + </Button> + </div> + </div> + </Shell> + ) + } + + // User is logged in, proceed with vendor ID + const vendorId = session.user.companyId + + // Validate vendorId (should be a number) + const idAsNumber = Number(vendorId) + + console.log(idAsNumber) + + if (isNaN(idAsNumber)) { + // Handle invalid vendor ID (this shouldn't happen if authentication is working properly) + return ( + <Shell className="gap-6"> + <div className="flex items-center justify-between"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + 정기평가 + </h2> + </div> + </div> + <div className="flex flex-col items-center justify-center py-12 text-center"> + <div className="rounded-lg border border-dashed p-10 shadow-sm"> + <h3 className="mb-2 text-xl font-semibold">계정 오류</h3> + <p className="mb-6 text-muted-foreground"> + 업체 정보가 올바르게 설정되지 않았습니다. 관리자에게 문의하세요. + </p> + </div> + </div> + </Shell> + ) + } + + // If we got here, we have a valid vendor ID + const promises = Promise.all([ + getEvaluationSubmissions({ + ...search, + filters: validFilters, + }, idAsNumber) + ]) + + return ( + <Shell className="gap-2"> + <div className="flex items-center justify-between space-y-2"> + <div className="flex items-center justify-between space-y-2"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + 정기평가 + </h2> + <p className="text-muted-foreground"> + 요청된 정기평가를 입력하고 제출할 수 있습니다. + </p> + </div> + </div> + </div> + + <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> + {/* DateRangePicker can go here */} + </React.Suspense> + + <React.Suspense + fallback={ + <DataTableSkeleton + columnCount={6} + searchableColumnCount={1} + filterableColumnCount={2} + cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} + shrinkZero + /> + } + > + <EvaluationSubmissionsTable promises={promises} /> + </React.Suspense> + </Shell> + ) +}
\ No newline at end of file diff --git a/app/[lng]/partners/(partners)/rfq-ship/[id]/page.tsx b/app/[lng]/partners/(partners)/rfq-ship/[id]/page.tsx index 772a9840..7f5400f3 100644 --- a/app/[lng]/partners/(partners)/rfq-ship/[id]/page.tsx +++ b/app/[lng]/partners/(partners)/rfq-ship/[id]/page.tsx @@ -50,7 +50,7 @@ export default async function VendorQuotationPage({ params }: PageProps) { with: { rfq: true, // 관계 설정 필요 vendor: true, // 관계 설정 필요 - // items: true, // 관계 설정 필요 + items: true, // 관계 설정 필요 } }) |
